package server

import (
	"context"
	"fmt"
	"time"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/mount"
	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/go-connections/nat"
	"github.com/docker/go-units"

	econd "gopkg.openfuyao.cn/bkeadm/pkg/executor/containerd"
	"gopkg.openfuyao.cn/bkeadm/pkg/executor/docker"
	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/pkg/infrastructure"
	"gopkg.openfuyao.cn/bkeadm/utils"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

func StartNFSServer(name, image, nfsDataDirectory string) error {
	var err error
	if infrastructure.IsContainerd() && !infrastructure.IsDocker() {
		return startNFSServerWithContainerd(name, image, nfsDataDirectory)
	}

	if !infrastructure.IsDocker() {
		log.BKEFormat(log.ERROR, "docker or containerd runtime not found.")
		return nil
	}

	err = global.Docker.EnsureImageExists(docker.ImageRef{Image: image},
		utils.NewRetryOptions(utils.MaxRetryCount, utils.DelayTime))
	if err != nil {
		return err
	}
	serverRunFlag, err := global.Docker.EnsureContainerRun(name)
	if err != nil {
		return err
	}
	// 服务已经运行
	if serverRunFlag {
		log.BKEFormat(log.INFO, "The nfs service is already running. ")
		return nil
	}
	// Starting the image repository
	err = global.Docker.Run(
		&container.Config{
			Image: image,
			ExposedPorts: map[nat.Port]struct{}{
				"2049/tcp": {},
			},
			Env: []string{
				"SHARED_DIRECTORY=/nfsshare",
				"FILEPERMISSIONS_UID=0",
				"FILEPERMISSIONS_GID=0",
				"FILEPERMISSIONS_MODE=0755",
			},
		},
		&container.HostConfig{
			Mounts: []mount.Mount{
				{
					Type:   mount.TypeBind,
					Source: nfsDataDirectory,
					Target: "/nfsshare",
				},
			},
			Privileged: true,
			PortBindings: map[nat.Port][]nat.PortBinding{
				nat.Port("2049/tcp"): {
					{HostIP: "0.0.0.0",
						HostPort: "2049",
					},
				},
			},
			RestartPolicy: container.RestartPolicy{
				Name:              "always",
				MaximumRetryCount: 0,
			},
			CapAdd: strslice.StrSlice{"SYS_ADMIN", "SETPCAP"},
			Resources: container.Resources{
				Ulimits: []*units.Ulimit{
					&units.Ulimit{
						Name: "nofile",
						Hard: 65536,
						Soft: 65536,
					},
				},
			},
		}, nil, nil, name)
	if err != nil {
		log.BKEFormat(log.WARN, "The nfs repository service fails to be deployed")
		return err
	}
	client := global.Docker.GetClient()
	for {
		log.BKEFormat(log.INFO, "Wait for the nfs mirroring service to start...")
		time.Sleep(5 * time.Second)
		containerInfo, err := client.ContainerInspect(context.Background(), name)
		if err != nil {
			continue
		}
		if containerInfo.ContainerJSONBase == nil {
			continue
		}
		if containerInfo.State.Running {
			break
		}
	}
	log.BKEFormat(log.INFO, "The nfs mirroring service is started. ")
	return nil
}

func startNFSServerWithContainerd(name, image, nfsDataDirectory string) error {
	err := econd.EnsureImageExists(image)
	if err != nil {
		return err
	}

	serverRunFlag, err := econd.EnsureContainerRun(name)
	if err != nil {
		return err
	}
	// 服务已经运行
	if serverRunFlag {
		log.BKEFormat(log.INFO, "The nfs service is already running. ")
		return nil
	}
	script := []string{
		"run", "-d", fmt.Sprintf("--name=%s", name), "--privileged",
		"-p", "2049:2049", "--restart=always", "-e", "SHARED_DIRECTORY=/nfsshare",
		"-e", "FILEPERMISSIONS_UID=0", "-e", "FILEPERMISSIONS_GID=0", "-e", "FILEPERMISSIONS_MODE=0755",
		"-v", fmt.Sprintf("%s:/nfsshare", nfsDataDirectory),
		image,
	}
	err = econd.Run(script)
	if err != nil {
		log.BKEFormat(log.WARN, "The nfs repository service fails to be deployed")
		return err
	}

	for {
		log.BKEFormat(log.INFO, "Wait for the nfs mirroring service to start...")
		time.Sleep(5 * time.Second)
		info, err := econd.ContainerInspect(name)
		if err != nil {
			continue
		}
		if info.State.Running {
			break
		}
	}
	log.BKEFormat(log.INFO, "The nfs mirroring service is started. ")
	return nil
}

func RemoveNFSServer(name string) error {
	log.BKEFormat(log.INFO, "Remove the nfs repository")

	if infrastructure.IsDocker() {
		for i := 0; i < 2; i++ {
			_ = global.Docker.ContainerRemove(name)
			_ = global.Command.ExecuteCommand("sh", "-c", "ps aux | grep nfsd | awk '{print $2}' | xargs kill -9")
			_, exist := global.Docker.ContainerExists(name)
			if exist {
				time.Sleep(3 * time.Second)
			} else {
				break
			}
		}
		return nil
	}
	if infrastructure.IsContainerd() {
		for i := 0; i < 2; i++ {
			_ = econd.ContainerRemove(name)
			_ = global.Command.ExecuteCommand("sh", "-c", "ps aux | grep nfsd | awk '{print $2}' | xargs kill -9")
			_, exist := econd.ContainerExists(name)
			if exist {
				time.Sleep(3 * time.Second)
			} else {
				break
			}
		}
		return nil
	}
	return nil
}
